<?php
require_once 'Junction/Core/Factory.php';
require_once 'Junction/Clause.php';
require_once 'Junction/Clause/Conjunction.php';
require_once 'Junction/Query.php';
require_once 'Junction/Query/Select.php';
require_once 'Junction/Db/Factory.php';
require_once 'Junction/Mapping.php';

class Junction_Core_Session {
    
    /**
     * @var Junction_Db_Service
     */
    private $_dbh;
    
    public function __construct() {
        $this->_dbh = Junction_Db_Factory::construct();
    }
    
    public function loadWhere($classname, Junction_Clause $clause = null) {
        $mapping = Junction_Core_Factory::construct($classname);
        
        $query = $mapping->buildQueryUsing(new Junction_Query_Select());
        if (isset($clause)) {
            $query->bindClause($clause);
        }
        $results = $this->_dbh->select($query->toSql(), $query->getParameters());
        return $this->rowsToObjects($results, $mapping);
    }
    
    // convert records to objects, load any references
    private function rowsToObjects($records, Junction_Mapping $mapping) {
        $objects = array();
        foreach ($records as $record) {
            $objects[] = $this->rowToObject($record, $mapping);
        }
        return $objects;
    }
    
    // set scalar values then load references
    private function rowToObject($record, Junction_Mapping $mapping) {
        $object = Junction_Utils_Reflection::getInstance($mapping->getName(), array());
        
        foreach ($record as $column => $value) {
            $mapping->setValueForColumn($object, $column, $value);
        }
        foreach ($mapping->getReferences() as $property => $ref) {
            $refObjects = $this->loadReferenceFor($object, $ref->getClassname());
            $mapping->setValueFor($object, $property, $refObjects);
        }
        Junction_Tracker::trackChangesFor($object);
        return $object;
    }
    
    private function loadReferenceFor($object, $classname) {
        $mapping = Junction_Core_Factory::construct($object);
        $refMapping = Junction_Core_Factory::construct($classname);
        $clause = new Junction_Clause_Conjunction();
        $clause->bindCondition($refMapping->getTablename() . '.' . $mapping->getKeyColumn() . ' = ?',
                $mapping->getValueForColumn($object, $mapping->getKeyColumn()));
        return $this->loadWhere($classname, $clause);
    }
    
    public function save($object) {
        $mapping = Junction_Core_Factory::construct($object);
        if (Junction_Tracker::hasBeenLogged($object)) {
            return $this->update($object);
        }
        return $this->insert($object);
    }
    
    private function update($object) {
        $mapping = Junction_Core_Factory::construct($object);
        $updated = false;
        if (Junction_Tracker::hasShallowChanges($object)) {
            $query = $mapping->buildQueryUsing(new Junction_Query_Update(), $object);
            $query->bindClause($mapping->buildWhereClause($object));
            $updated = $this->_dbh->save($query->toSql(), $query->getParameters());
        }
        if (Junction_Tracker::hasDeepChanges($object)) {
            $this->saveReferencesFor($object, $mapping);
            $updated = true;
        }
        if ($updated) {
            Junction_Tracker::trackChangesFor($object);
            return true;
        }
        return false;
    }
    
    private function insert($object) {
        $mapping = Junction_Core_Factory::construct($object);
        $query = $mapping->buildQueryUsing(new Junction_Query_Insert(), $object);
        $saved = false;
        if ($this->_dbh->save($query->toSql(), $query->getParameters())) {
            $mapping->setValueFor($object,
                $mapping->getKeyProperty(),
                $this->_dbh->lastInsertId());
            $saved = $this->saveReferencesFor($object, $mapping);
            Junction_Tracker::trackChangesFor($object);
        }
        return $saved;
    }
    
    private function saveReferencesFor($object, Junction_Mapping $mapping) {
        $saved = true;
        // foreach reference property
        foreach ($mapping->getReferences() as $property => $ref) {
            $refMapping = Junction_Core_Factory::construct($ref->getClassname());
            // TODO what if ref object is not a collection?
            $refObjects = $mapping->getValueFor($object, $property);
            // foreach reference object
            foreach ($refObjects as $refObject) {
                // set the join column's value on the dependent object
                $value = $mapping->getValueFor($object, $mapping->getKeyProperty());
                $refMapping->setValueForColumn($refObject, $mapping->getKeyColumn(), $value);
                $saved = $this->save($refObject);
            }
        }
        return $saved;
    }
    
    /**
     * @param Junction_Clause $clause
     * @return int
     */
    public function deleteWhere($classname, Junction_Clause $clause = null) {
        $mapping = Junction_Core_Factory::construct($classname);
        $query = $mapping->buildQueryUsing(new Junction_Query_Delete());
        if (isset($clause)) {
            $query->bindClause($clause);
        }
        if ($this->_dbh->save($query->toSql(), $query->getParameters())) {
            return $this->_dbh->affectedRows();
        }
        return 0;
    }
}
?>